﻿namespace lucere.test.entity
{
    using System;
    using MbUnit.Framework;
    using Moq;
    
    using lucere.entity;
    using toolkit.extension;
    using analysis.query;
    using index.write;
    using lucere.io;
    using service.search;

    [TestFixture]
    public class TestDocument
    {
        const string BinaryVal = "this text will be stored as a byte array in the index";
        const string BinaryVal2 = "this text will be also stored and compressed as a byte array in the index";

        private const string FieldNameBinary = "binary";
        private const string FieldNameString = "string";
        private const string FieldNameKeyword = "keyword";
        private const string FieldNameDoesNotExist = "doesnotexists";
        private const string FieldNameText = "text";
        private const string FieldNameUnindexed = "unindexed";
        private const string FieldNameUnstored = "unstored";

        private const string FieldValueTest1 = "test1";
        private const string FieldValueTest2 = "test2";
        private const string FieldNameId = "id";
        private const string FieldValueId = "id1";
        private const string FieldValueId2 = "id2";
        private const string FieldValueId3 = "id3";

        private static IDocument GetDocument()
        {
            throw new NotImplementedException();
        }

        [Test]
        public void TestBinaryField()
        {
            IDocument document = GetDocument();

            var moqStringFieldStoredNotIndexed = ConfigureStringFieldStoredNotIndexed(FieldNameString, BinaryVal);
            var moqBinaryFieldStored = ConfigureBinaryFieldStored(FieldNameBinary, BinaryVal.GetBytes());
            var moqBinarySecondFieldStored = ConfigureBinaryFieldStored(FieldNameBinary, BinaryVal2.GetBytes());

            var stringFieldStoredNotIndexed = moqStringFieldStoredNotIndexed.Object;
            var binaryFieldStored = moqBinaryFieldStored.Object;
            var binarySecondFieldStored = moqBinarySecondFieldStored.Object;

            document.Add(stringFieldStoredNotIndexed);
            document.Add(binaryFieldStored);
            
            Assert.AreEqual(2, document.Fields.Count);

            Assert.IsTrue(binaryFieldStored.IsBinary);
            Assert.IsTrue(binaryFieldStored.IsStored);
            Assert.IsFalse(binaryFieldStored.IsIndexed);
            Assert.IsFalse(binaryFieldStored.IsTokenized);

            string binaryTest = BitConverter.ToString(document.GetBinaryValue(FieldNameBinary));
            Assert.IsTrue(binaryTest.Equals(BinaryVal));

            string stringTest = document.Get(FieldNameString);
            Assert.IsTrue(binaryTest.Equals(stringTest));

            document.Add(binarySecondFieldStored);

            Assert.AreEqual(3, document.Fields.Count);

            byte[][] binaryTests = document.GetBinaryValues(FieldNameBinary);

            Assert.AreEqual(2, binaryTests.Length);

            binaryTest = BitConverter.ToString(binaryTests[0]);
            string binaryTest2 = BitConverter.ToString(binaryTests[1]);

            Assert.IsFalse(binaryTest.Equals(binaryTest2));

            Assert.IsTrue(binaryTest.Equals(BinaryVal));
            Assert.IsTrue(binaryTest2.Equals(BinaryVal2));

            document.RemoveField(FieldNameString);
            Assert.AreEqual(2, document.Fields.Count);

            document.RemoveFields(FieldNameBinary);
            Assert.AreEqual(0, document.Fields.Count);
        }

        [Test]
        public void TestConstructorExceptionsStoreAndIndex()
        {
            var fieldTest = ConstructIFieldable("name", "value", FieldStore.Yes, FieldIndex.No); // ok
            fieldTest = ConstructIFieldable("name", "value", FieldStore.No, FieldIndex.NotAnalyzed); // ok

            try
            {
                fieldTest = ConstructIFieldable("name", "value", FieldStore.No, FieldIndex.No);
                Assert.Fail();
            }
            catch (ArgumentException)
            {
            }

        }


        public void TestConstructorExceptionsTermVectors()
        {
            var fieldTest = ConstructIFieldable("name", "value", FieldStore.Yes, FieldIndex.No, FieldTermVector.No); // ok

            try
            {
                fieldTest = ConstructIFieldable("name", "value", FieldStore.Yes, FieldIndex.No, FieldTermVector.Yes); // Fail
                Assert.Fail();
            }
            catch (ArgumentException)
            {
                
            }
        }

        private static IFieldable ConstructIFieldable(string fieldName, string fieldValue, FieldStore fieldStore, FieldIndex fieldIndex, FieldTermVector fieldTermVector)
        {
            throw new NotImplementedException();
        }

        private static IFieldable ConstructIFieldable(string fieldName, string fieldValue, FieldStore fieldStore, FieldIndex fieldIndex)
        {
            throw new NotImplementedException();
        }

        private static Mock<IField> ConfigureBinaryFieldStored(string fieldName, byte[] fieldValue)
        {
            var moqBinaryFieldStored = new Mock<IField>();

            // Name
            moqBinaryFieldStored.SetupGet(binField => binField.Name).Returns(fieldName);

            // Value
            moqBinaryFieldStored.SetupGet(binField => binField.FieldsData).Returns(fieldValue);

            // Byte[] .ctor
            moqBinaryFieldStored.SetupGet(binField => binField.IsStored).Returns(true);
            moqBinaryFieldStored.SetupGet(binField => binField.IsIndexed).Returns(false);
            moqBinaryFieldStored.SetupGet(binField => binField.IsTokenized).Returns(false);
            moqBinaryFieldStored.SetupGet(binField => binField.OmitTermFreqAndPositions).Returns(false);
            moqBinaryFieldStored.SetupGet(binField => binField.OmitNorms).Returns(true);

            // From Binary data
            moqBinaryFieldStored.SetupGet(binField => binField.IsBinary).Returns(true);
            moqBinaryFieldStored.SetupGet(binField => binField.BinaryLength).Returns(fieldValue.Length);
            moqBinaryFieldStored.SetupGet(binField => binField.BinaryOffset).Returns(0);

            // TermVector.NO
            moqBinaryFieldStored.SetupGet(strField => strField.IsTermVectorStored).Returns(false);
            moqBinaryFieldStored.SetupGet(strField => strField.IsStoreOffsetWithTermVector).Returns(false);
            moqBinaryFieldStored.SetupGet(strField => strField.IsStorePositionWithTermVector).Returns(false);

            return moqBinaryFieldStored;
        }

        private static Mock<IField> ConfigureStringFieldStoredNotIndexed(string fieldName, string fieldValue)
        {
            var moqStringFieldStoredNotIndexed = new Mock<IField>();

            // Name
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.Name).Returns(fieldName);
            
            // Value
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.FieldsData).Returns(fieldValue);
            
            // Stored.YES
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsStored).Returns(true);
            
            // Indexed.NO
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsIndexed).Returns(false);
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.OmitNorms).Returns(true);
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsTokenized).Returns(false);
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.OmitTermFreqAndPositions).Returns(false);
            
            // String overload .ctor
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsBinary).Returns(false);

            // TermVector.NO
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsTermVectorStored).Returns(false);
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsStoreOffsetWithTermVector).Returns(false);
            moqStringFieldStoredNotIndexed.SetupGet(strField => strField.IsStorePositionWithTermVector).Returns(false);
            
            return moqStringFieldStoredNotIndexed;
        }

        private static Mock<IField> ConfigureStringFieldStoredNotAnalyzed(string fieldName, string fieldValue)
        {
            var configureStringFieldStoredNotAnalyzed = new Mock<IField>();

            // Name
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.Name).Returns(fieldName);

            // Value
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.FieldsData).Returns(fieldValue);

            // Stored.YES
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStored).Returns(true);

            // Indexed.NOT_ANALYZED
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsIndexed).Returns(true);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.OmitNorms).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTokenized).Returns(false);

            // String overload .ctor
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsBinary).Returns(false);

            // TermVector.NO
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTermVectorStored).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStoreOffsetWithTermVector).Returns(
                false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStorePositionWithTermVector).Returns(
                false);

            return configureStringFieldStoredNotAnalyzed;
        }

        private static Mock<IField> ConfigureStringFieldNotStoredAnalyzed(string fieldName, string fieldValue)
        {
            var configureStringFieldStoredNotAnalyzed = new Mock<IField>();

            // Name
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.Name).Returns(fieldName);

            // Value
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.FieldsData).Returns(fieldValue);

            // Stored.NO
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStored).Returns(false);

            // Indexed.ANALYZED
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsIndexed).Returns(true);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.OmitNorms).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTokenized).Returns(true);

            // String overload .ctor
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsBinary).Returns(false);

            // TermVector.NO
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTermVectorStored).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStoreOffsetWithTermVector).Returns(
                false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStorePositionWithTermVector).Returns(
                false);

            return configureStringFieldStoredNotAnalyzed;
        }

        private static Mock<IField> ConfigureStringFieldStoredAnalyzed(string fieldName, string fieldValue)
        {
            var configureStringFieldStoredNotAnalyzed = new Mock<IField>();

            // Name
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.Name).Returns(fieldName);

            // Value
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.FieldsData).Returns(fieldValue);

            // Stored.YES
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStored).Returns(true);

            // Indexed.ANALYZED
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsIndexed).Returns(true);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.OmitNorms).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTokenized).Returns(true);

            // String overload .ctor
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsBinary).Returns(false);

            // TermVector.NO
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsTermVectorStored).Returns(false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStoreOffsetWithTermVector).Returns(
                false);
            configureStringFieldStoredNotAnalyzed.SetupGet(strField => strField.IsStorePositionWithTermVector).Returns(
                false);

            return configureStringFieldStoredNotAnalyzed;
        }

        private static IDocument MakeDocumentWithFields()
        {
            IDocument documentWithFields = GetDocument();
            
            var moqKeywordTest1StoredNotAnalyzed = ConfigureStringFieldStoredNotAnalyzed(FieldNameKeyword, FieldValueTest1);
            var keywordTest1 = moqKeywordTest1StoredNotAnalyzed.Object;

            var moqKeywordTest2StoredNotAnalyzed = ConfigureStringFieldStoredNotAnalyzed(FieldNameKeyword, FieldValueTest2);
            var keywordTest2 = moqKeywordTest2StoredNotAnalyzed.Object;

            var moqTextTest1StoredAnalyzed = ConfigureStringFieldStoredAnalyzed(FieldNameText, FieldValueTest1);
            var textTest1 = moqTextTest1StoredAnalyzed.Object;

            var moqTextTest2StoredAnalyzed = ConfigureStringFieldStoredAnalyzed(FieldNameText, FieldValueTest2);
            var textTest2 = moqTextTest2StoredAnalyzed.Object;

            var moqUnindexedTest1 = ConfigureStringFieldStoredNotIndexed(FieldNameUnindexed, FieldValueTest1);
            var unindexedTest1 = moqUnindexedTest1.Object;

            var moqUnindexedTest2 = ConfigureStringFieldStoredNotIndexed(FieldNameUnindexed, FieldValueTest2);
            var unindexedTest2 = moqUnindexedTest2.Object;

            var moqUnstoredTest1 = ConfigureStringFieldNotStoredAnalyzed(FieldNameUnstored, FieldValueTest1);
            var unstoredTest1 = moqUnstoredTest1.Object;

            var moqUnstoredTest2 = ConfigureStringFieldNotStoredAnalyzed(FieldNameUnstored, FieldValueTest2);
            var unstoredTest2 = moqUnstoredTest2.Object;

            documentWithFields.Add(keywordTest1);
            documentWithFields.Add(keywordTest2);
            documentWithFields.Add(textTest1);
            documentWithFields.Add(textTest2);
            documentWithFields.Add(unindexedTest1);
            documentWithFields.Add(unindexedTest2);
            documentWithFields.Add(unstoredTest1);
            documentWithFields.Add(unstoredTest2);

            return documentWithFields;
        }

        /// <summary>
        /// Tests Document.RemoveField(string) method for a brand new Document that
        /// has not been indexed yet.
        /// </summary>
        [Test]
        public void TestRemoveForNewDocument()
        {
            IDocument doc = MakeDocumentWithFields();
            Assert.AreEqual(8, doc.Fields.Count);
            doc.RemoveFields(FieldNameKeyword);
            Assert.AreEqual(6, doc.Fields.Count);
            doc.RemoveFields(FieldNameDoesNotExist);   //removing non-existing fields is silently ignored
            doc.RemoveFields(FieldNameKeyword);        //removing a field more than once
            Assert.AreEqual(6, doc.Fields.Count);
            doc.RemoveField(FieldNameText);
            Assert.AreEqual(5, doc.Fields.Count);
            doc.RemoveField(FieldNameText);
            Assert.AreEqual(4, doc.Fields.Count);
            doc.RemoveField(FieldNameText);
            Assert.AreEqual(4, doc.Fields.Count);
            doc.RemoveField(FieldNameDoesNotExist);   //removing non-existing fields is silently ignored
            Assert.AreEqual(4, doc.Fields.Count);
            doc.RemoveFields(FieldNameUnindexed);
            Assert.AreEqual(2, doc.Fields.Count);
            doc.RemoveFields(FieldNameUnstored);
            Assert.AreEqual(0, doc.Fields.Count);
            doc.RemoveFields(FieldNameDoesNotExist);  //removing non-existing fields is silently ignored
            Assert.AreEqual(0, doc.Fields.Count);
        }

        /// <summary>
        /// Tests IDocument.GetValues(string) method for a brand new Document that
        /// has not been indexed yet
        /// </summary>
        [Test]
        public void TestGetValuesForNewDocument()
        {
            DoAssert(MakeDocumentWithFields(), false);
        }

        private static void DoAssert(IDocument doc, bool fromIndex)
        {
            string[] keywordFieldValues = doc.GetValues(FieldNameKeyword);
            string[] textFieldValues = doc.GetValues(FieldNameText);
            string[] unindexedFieldValues = doc.GetValues(FieldNameUnindexed);
            string[] unstoredFieldValues = doc.GetValues(FieldNameUnstored);

            Assert.IsTrue(keywordFieldValues.Length == 2);
            Assert.IsTrue(textFieldValues.Length == 2);
            Assert.IsTrue(unindexedFieldValues.Length == 2);
            //this test cannot work for documents retrieved from the index
            //since unstored fields will obviously not be returned
            if (!fromIndex)
                Assert.IsTrue(unstoredFieldValues.Length == 2);

            Assert.IsTrue(keywordFieldValues[0].Equals(FieldValueTest1));
            Assert.IsTrue(keywordFieldValues[1].Equals(FieldValueTest2));
            Assert.IsTrue(textFieldValues[0].Equals(FieldValueTest1));
            Assert.IsTrue(textFieldValues[1].Equals(FieldValueTest2));
            Assert.IsTrue(unindexedFieldValues[0].Equals(FieldValueTest1));
            Assert.IsTrue(unindexedFieldValues[1].Equals(FieldValueTest2));
            //this test cannot work for documents retrieved from the index
            //since unstored fields will obviously not be returned
            if (!fromIndex)
            {
                Assert.IsTrue(unstoredFieldValues[0].Equals(FieldValueTest1));
                Assert.IsTrue(unstoredFieldValues[1].Equals(FieldValueTest2));
            }
        }

        [Test]
        public void TestGetValuesForIndexedDocument()
        {
            using (IDirectory dir = GetRAMDirectory())
            {
                using (IIndexWriter writer = GetIndexWriter(dir))
                {
                    writer.AddDocument(MakeDocumentWithFields());
                }

                using (ISearcher searcher = GetISearcher(dir))
                {
                    //search for something that does exist
                    IQuery query = GetIQuery(FieldNameKeyword, FieldValueTest1);

                    //ensure that queries return expected results without dateFilter first
                    IScoreDoc[] hits = searcher.Search(query, null, 1000).ScoreDocs;
                    Assert.AreEqual(1, hits.Length);

                    DoAssert(searcher.Doc(hits[0].Doc), true);
                }
            }
        }

        private static IQuery GetIQuery(string fieldName, string fieldValue)
        {
            throw new NotImplementedException();
            // ITerm termVector = new Term(fieldName, fieldValue);
            //return new TermQuery(termVector);
        }

        private static ISearcher GetISearcher(IDirectory directory)
        {
            throw new NotImplementedException();
            //return new IndexSearcher(directory, true);
        }

        private static IIndexWriter GetIndexWriter(IDirectory directory)
        {
            throw new NotImplementedException();
            //return new IndexWriter(directory,
            //                       new StandardAnalyzer(lucere.util.Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);
        }

        private static IDirectory GetRAMDirectory()
        {
            throw new NotImplementedException();
            //return new RAMDirectory();
        }


        [Test]
        public void TestFieldSetValue()
        {
            Mock<IField> idStringFieldStoredNotAnalyzed = ConfigureStringFieldStoredNotAnalyzed(FieldNameId, FieldValueId);
            Mock<IField> keywordStringFieldStoredNotAnalyzed = ConfigureStringFieldStoredNotAnalyzed(FieldNameKeyword, FieldValueTest1);

            IField field = idStringFieldStoredNotAnalyzed.Object;
            IField field1 = keywordStringFieldStoredNotAnalyzed.Object;

            IDocument doc = GetDocument();
            
            doc.Add(field);
            doc.Add(field1);

            using (IDirectory directory = GetRAMDirectory())
            {
                using (IIndexWriter writer = GetIndexWriter(directory))
                {
                    writer.AddDocument(doc);

                    field.SetValue(FieldValueId2);
                    writer.AddDocument(doc);

                    field.SetValue(FieldValueId3);
                    writer.AddDocument(doc);
                }

                using (ISearcher searcher = GetISearcher(directory))
                {
                    IQuery query = GetIQuery(FieldNameKeyword, FieldValueTest1);

                    //ensure that queries return expected results without DateFilter first
                    IScoreDoc[] hits = searcher.Search(query, null, 1000).ScoreDocs;
                    Assert.AreEqual(3, hits.Length);

                    int result = 0;
                    for (int i = 0; i < 3; i++)
                    {
                        int doc1Number = hits[i].Doc;

                        IDocument doc2 = searcher.Doc(doc1Number);
                        IField f = doc2.GetField(FieldNameId);
                        if (f.StringValue.Equals(FieldValueId))
                            result |= 1;
                        else if (f.StringValue.Equals(FieldValueId2))
                            result |= 2;
                        else if (f.StringValue.Equals(FieldValueId3))
                            result |= 4;
                        else
                            Assert.Fail("unexpected id field");
                    }

                    Assert.AreEqual(7, result, "did not see all IDs");

                }
            }

            
        }

        [Test]
        public void TestFieldSetValueChangeBinary()
        {
            IField field1 = GetConcreteBinaryField("field1", new byte[0], FieldStore.Yes);
            IField field2 = GetConreteTextField("field2", "", FieldStore.Yes, FieldIndex.Analyzed);

            try
            {
                field1.SetValue("abc");
                Assert.Fail("did not hit expected exception");
            }
            catch (ArgumentException)
            { }

            try
            {
                field2.SetValue(new byte[0]);
                Assert.Fail("did not hit expected exception");
            }
            catch (ArgumentException)
            { }
        }

        private static IField GetConreteTextField(string fieldName, string fieldValue, FieldStore fieldStore, FieldIndex fieldIndex)
        {
            // return new Field("field2", "", Store.Yes, Index.Analyzed);
            throw new NotImplementedException();
        }

        private static IField GetConcreteBinaryField(string fieldName, byte[] fieldValue, FieldStore fieldStore)
        {
            // return new Field("field1", new byte[0], Store.Yes); 
            throw new NotImplementedException();
        }
    }
}
